﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;
using DevExpress.XtraGrid.Views.Grid;
using E4RPDevExpress10;
using E4RPPrintManager10;
using E4RPTools;
using Movements.DataSet;

namespace Movements
{
    public class Controller
    {
        private SqlConnection _connection;

        public IWin32Window OwnerWindow { get; set; }

        public long LoggedUserId { get; set; }

        public int LoggedUserProfile { get; set; }

        #region Private Methods

        private void PublishAndShowErrorMessage(Exception e, string errorMessage)
        {
            ExceptionPublisher.Publish(ConfigurationManager.AppSettings["LogFile"], e);
            ExceptionPublisher.ShowErrorMessage(OwnerWindow, errorMessage, "Gestão Movimentos");
        }

        private void FixConnection()
        {
            if (_connection.State == ConnectionState.Open)
                _connection.Close();
            _connection.Open();
        }
                
        private static void ClearErrors(DataTable table)
        {
            foreach (DataRow row in table.Rows)
                row.ClearErrors();
        }

        private static string GetPrinterName()
        {
            string printerName = ConfigurationManager.AppSettings["PrinterName"];
            if (!string.IsNullOrEmpty(printerName))
                return printerName;
            var printManager = new PrintManager();
            return printManager.GetDefaultPrinterName();
        }

        #endregion 

        public bool IsUserValid(string userName, string password)
        {
            var dataSource = ConfigurationManager.AppSettings["FonteDados"];
            var databaseName = ConfigurationManager.AppSettings["NomeBaseDados"];
            var connectionString = String.Format("Data Source={0};Initial Catalog={1};User ID={2};Password={3};MultipleActiveResultSets=True;", dataSource, databaseName, userName, password);
            try
            {
                _connection = new SqlConnection(connectionString);
                _connection.Open();
            }
            catch (Exception e)
            {
                PublishAndShowErrorMessage(e, "Utilizador sem acesso à base de dados.");
                return false;
            }
            return true;
        }

        public void SetLoggedUserProfile()
        {
            LoggedUserProfile = GetUsers().Utilizador.FindById(LoggedUserId).Perfil;
        }

        #region User

        private void OnUserSaved()
        {
            EventHandler<EventArgs> saved = UserSaved;
            if (saved != null)
                saved(this, new EventArgs());
        }

        public event EventHandler<EventArgs> UserSaved;

        public UtilizadorDataSet GetUsers()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetUsers(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a lista de utilizadores.");
                return new UtilizadorDataSet();
            }
        }

        public UtilizadorDataSet GetUser(long userId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetUser(sqlTransaction, userId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler o utilizador.");
                return new UtilizadorDataSet();
            }
        }

        public Result IsUserValid(UtilizadorDataSet dataset)
        {
            var result = new Result();
            var user = dataset.Utilizador[0];
            if (user.IsNomeNull() || user.Nome == "") result.Add("Nome", "Por favor, preencha o nome.");
            if (!user.IsNomeNull() && user.Nome.Length > 50) result.Add("Nome", "O nome excede os 50 permitidos.");
            if (user.IsPasswordNull() || user.Password == "") result.Add("Password", "Por favor, preencha a password.");
            if (!user.IsPasswordNull() && user.Password.Length > 50) result.Add("Password", "A password excede os 50 caracteres permitidos.");
            return result;
        }

        public void SaveUser(UtilizadorDataSet dataset)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.SaveUser(sqlTransaction, dataset.Utilizador[0]);
                sqlTransaction.Commit();
                OnUserSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível gravar o utilizador.");
            }
        }

        #endregion

        #region Supplier

        private void OnSupplierSaved()
        {
            EventHandler<EventArgs> saved = SupplierSaved;
            if (saved != null)
                saved(this, new EventArgs());
        }

        public event EventHandler<EventArgs> SupplierSaved;

        public FornecedorDataSet GetSuppliers()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetSuppliers(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a lista de fornecedores.");
                return new FornecedorDataSet();
            }
        }

        public FornecedorDataSet GetSupplier(long supplierId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetSupplier(sqlTransaction, supplierId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler o fornecedor.");
                return new FornecedorDataSet();
            }
        }

        public Result IsSupplierValid(FornecedorDataSet dataset)
        {
            var result = new Result();
            var supplier = dataset.Fornecedor[0];
            if (supplier.IsNomeNull() || supplier.Nome == "") result.Add("Nome", "Por favor, preencha o nome.");
            if (!supplier.IsNomeNull() && supplier.Nome.Length > 150) result.Add("Nome", "O nome excede os 150 caracteres permitidos."); 
            if (!supplier.IsContribuinteNull() && supplier.Contribuinte.Length > 50) result.Add("Contribuinte", "O nome excede os 50 caracteres permitidos."); 
            if (!supplier.IsMoradaNull() && supplier.Morada.Length > 150) result.Add("Morada", "A morada excede os 150 caracteres permitidos."); 
            if (!supplier.IsCodigoPostalNull() && supplier.CodigoPostal.Length > 150) result.Add("CodigoPostal", "O código postal excede os 150 caracteres permitidos."); 
            if (!supplier.IsTelefoneNull() && supplier.Telefone.Length > 50) result.Add("Telefone", "O telefone excede os 50 caracteres permitidos.");             
            if (!supplier.IsTelemovelNull() && supplier.Telemovel.Length > 50) result.Add("Telemovel", "O telemóvel excede os 50 caracteres permitidos."); 
            if (!supplier.IsEmailNull() && supplier.Email.Length > 150) result.Add("Email", "O email excede os 150 caracteres permitidos."); 
            if (!supplier.IsContactoNull() && supplier.Contacto.Length > 150) result.Add("Contacto", "O contacto excede os 150 caracteres permitidos."); 
            return result;
        }

        public void SaveSupplier(FornecedorDataSet dataset)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.SaveSupplier(sqlTransaction, dataset.Fornecedor[0]);
                sqlTransaction.Commit();
                OnSupplierSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível gravar o fornecedor.");
            }
        }

        #endregion

        #region TargetEntity

        private void OnTargetEntitySaved()
        {
            EventHandler<EventArgs> saved = TargetEntitySaved;
            if (saved != null)
                saved(this, new EventArgs());
        }


        public event EventHandler<EventArgs> TargetEntitySaved;

        public EntidadeDestinoDataSet GetTargetEntities()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetTargetEntities(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a lista de entidades destino.");
                return new EntidadeDestinoDataSet();
            }
        }

        public EntidadeDestinoDataSet GetTargetEntity(long targetEntityId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetTargetEntity(sqlTransaction, targetEntityId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a entidade destino.");
                return new EntidadeDestinoDataSet();
            }
        }

        public Result IsTargetEntityValid(EntidadeDestinoDataSet dataset)
        {
            var result = new Result();
            var targetEntity = dataset.EntidadeDestino[0];
            if (targetEntity.IsNomeNull() || targetEntity.Nome == "") result.Add("Nome", "Por favor, preencha o nome.");
            if (!targetEntity.IsNomeNull() && targetEntity.Nome.Length > 50) result.Add("Nome", "O nome excede os 50 caracteres permitidos.");
            return result;
        }

        public void SaveTargetEntity(EntidadeDestinoDataSet dataset)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.SaveTargetEntity(sqlTransaction, dataset.EntidadeDestino[0]);
                sqlTransaction.Commit();
                OnTargetEntitySaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível gravar a entidade destino.");
            }
        }

        public bool CanDeleteTargetEntity(long targetEntityId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var result = Database.ExistsMovements(sqlTransaction, targetEntityId);
                sqlTransaction.Commit();
                return !result;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível validar se existem movimentos associados à entidade destino.");
                return false;
            }
        }

        public void DeleteTargetEntity(long targetEntityId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.DeleteTargetEntity(sqlTransaction, targetEntityId);
                sqlTransaction.Commit();
                OnTargetEntitySaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível eliminar a entidade destino.");                
            }
        }

        #endregion

        #region Product

        private void OnProductSaved()
        {
            EventHandler<EventArgs> saved = ProductSaved;
            if (saved != null)
                saved(this, new EventArgs());
        }

        public event EventHandler<EventArgs> ProductSaved;

        public ProdutoDataSet GetProducts()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetProducts(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a lista de produtos.");
                return new ProdutoDataSet();
            }
        }

        public ProdutoDataSet GetProduct(long productId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetProduct(sqlTransaction, productId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler o produto.");
                return new ProdutoDataSet();
            }
        }

        private static bool IsSupplierDuplicated(ProdutoDataSet.FornecedorDataTable suppliers, long supplierId)
        {
            var counter = 0;
            foreach (ProdutoDataSet.FornecedorRow productSupplier in suppliers)
            {
                if (productSupplier.RowState == DataRowState.Deleted)
                    continue;
                if (productSupplier.IsNull("IdFornecedor")) 
                    continue;
                if (productSupplier.IdFornecedor == supplierId)
                    counter++;
            }
            return counter > 1;
        }

        private bool HasMovementAssociated(DataRow productSupplier)
        {
            var productId = Convert.ToInt64(productSupplier["IdProduto", DataRowVersion.Original]);
            var supplierId = Convert.ToInt64(productSupplier["IdFornecedor", DataRowVersion.Original]);
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var result = Database.ExistsMovements(sqlTransaction, productId, supplierId);
                sqlTransaction.Commit();
                return result;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível validar se existem movimentos associados ao produto/fornecedor.");
                return true;
            }
        }

        public Result IsProductValid(ProdutoDataSet dataset)
        {
            var result = new Result();
            var product = dataset.Produto[0];
            if (product.IsReferenciaNull() || product.Referencia == "") result.Add("Referencia", "Por favor, preencha a referencia.");
            if (!product.IsReferenciaNull() && product.Referencia.Length > 150) result.Add("Referencia", "A referência excede os 50 caracteres permitidos.");
            if (product.IsDesignacaoNull() || product.Designacao == "") result.Add("Designacao", "Por favor, preencha a designação.");
            if (!product.IsDesignacaoNull() && product.Designacao.Length > 150) result.Add("Designacao", "A designação excede os 150 caracteres permitidos.");
            if (!product.IsUnidadeNull() && product.Unidade.Length > 150) result.Add("Unidade", "A unidade excede os 50 caracteres permitidos.");
            ClearErrors(dataset.Fornecedor);
            foreach (ProdutoDataSet.FornecedorRow productSupplier in dataset.Fornecedor)
            {
                if (productSupplier.RowState == DataRowState.Deleted)
                {
                    if(HasMovementAssociated(productSupplier))
                    {
                        productSupplier.SetColumnError("IdFornecedor", "O fornecedor não pode ser eliminado, pois é usado.");
                        result.Add("Fornecedor.IdFornecedor", "Por favor, preencha o fornecedor.");
                        productSupplier.RejectChanges();
                    }
                    else
                        continue;       
                }
                    
                if(productSupplier.IsNull("IdFornecedor"))
                {
                    productSupplier.SetColumnError("IdFornecedor", "Por favor, preencha o fornecedor.");
                    result.Add("Fornecedor.IdFornecedor", "Por favor, preencha o fornecedor.");
                }
                if(!productSupplier.IsNull("IdFornecedor") && IsSupplierDuplicated(dataset.Fornecedor, productSupplier.IdFornecedor))
                {
                    productSupplier.SetColumnError("IdFornecedor", "O fornecedor está duplicado.");
                    result.Add("Fornecedor.IdFornecedor", "O fornecedor está duplicado.");
                }                
                if(!productSupplier.IsReferenciaFornecedorNull() && productSupplier.ReferenciaFornecedor.Length > 50)
                {
                    productSupplier.SetColumnError("ReferenciaFornecedor", "A referência excede os 50 caracteres permitidos.");
                    result.Add("Fornecedor.ReferenciaFornecedor", "A referência excede os 50 caracteres permitidos.");
                }
            }
            return result;
        }

        public void SaveProduct(ProdutoDataSet dataset)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                foreach(ProdutoDataSet.FornecedorRow supplier in dataset.Fornecedor)
                    if (supplier.RowState == DataRowState.Added || supplier.RowState == DataRowState.Modified)
                        supplier.DataActualizacao = DateTime.Now;
                Database.SaveProduct(sqlTransaction, dataset.Produto[0]);
                Database.UpdateProductSuppliers(sqlTransaction, dataset.Fornecedor);
                Database.UpdateProductTreeProduct(sqlTransaction, dataset.Produto[0].Id, dataset.Produto[0].Designacao);
                sqlTransaction.Commit();
                OnProductSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível gravar o produto.");
            }
        }

        #endregion

        public UnidadeProdutoDataSet GetProductUnits()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetProductUnits(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a lista unidades dos produtos.");
                return new UnidadeProdutoDataSet();
            }
        }

        public TipoMovimentoDataSet GetMovementTypes()
        {
            return Database.GetMovementTypes();
        }

        public PerfilUtilizadorDataSet GetProfiles()
        {
            return Database.GetProfiles();
        }

        public ProdutoFornecedorDataSet GetProductSupplier()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetProductSupplier(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a lista produtos e fornecedores.");
                return new ProdutoFornecedorDataSet();
            }
        }

        #region Movement

        private void OnMovementSaved()
        {
            EventHandler<EventArgs> saved = MovementSaved;
            if (saved != null)
                saved(this, new EventArgs());
        }
        
        private static void RemoveStocks(SqlTransaction sqlTransaction, MovimentoDataSet.MovimentoRow movement)
        {
            if (movement.RowState == DataRowState.Added)
                return;
            var oldMovement = Database.GetMovement(sqlTransaction, movement.Id);
            var signal = (oldMovement.Movimento[0].Tipo == "S" || oldMovement.Movimento[0].Tipo == "F") ? 1 : -1;
            foreach (MovimentoDataSet.ItemMovimentoRow oldMovementItem in oldMovement.ItemMovimento)
            {
                var stock = Database.GetStock(sqlTransaction, oldMovementItem.IdProduto);
                Database.UpdateStock(sqlTransaction, oldMovementItem.IdProduto, stock + (signal * oldMovementItem.Quantidade));
                var supplierStock = Database.GetStock(sqlTransaction, oldMovementItem.IdProduto, oldMovementItem.IdFornecedor);
                Database.UpdateStock(sqlTransaction, oldMovementItem.IdProduto, oldMovementItem.IdFornecedor, supplierStock + (signal * oldMovementItem.Quantidade));
            }
        }

        private static void AddStocks(SqlTransaction sqlTransaction, MovimentoDataSet.MovimentoRow movement)
        {
            var signal = (movement.Tipo == "S" || movement.Tipo == "F") ? -1 : 1;

            foreach (var movementItem in movement.GetItemMovimentoRows())
            {
                var stock = Database.GetStock(sqlTransaction, movementItem.IdProduto);
                Database.UpdateStock(sqlTransaction, movementItem.IdProduto, stock + signal * movementItem.Quantidade);
                var supplierStock = Database.GetStock(sqlTransaction, movementItem.IdProduto, movementItem.IdFornecedor);
                Database.UpdateStock(sqlTransaction, movementItem.IdProduto, movementItem.IdFornecedor, supplierStock + signal * movementItem.Quantidade);
            }
        }

        private static void UpdateValues(SqlTransaction sqlTransaction, MovimentoDataSet.MovimentoRow movement)
        {
            if(movement.RowState == DataRowState.Added)
            {
                foreach(MovimentoDataSet.ItemMovimentoRow movementItem in movement.GetItemMovimentoRows())
                    Database.UpdateValue(sqlTransaction, movementItem.IdProduto, movementItem.IdFornecedor, movementItem.Valor);
                return;
            }
            var oldMovement = Database.GetMovement(sqlTransaction, movement.Id);
            foreach (MovimentoDataSet.ItemMovimentoRow movementItem in movement.GetItemMovimentoRows())
            {
                if (movementItem.RowState == DataRowState.Added)
                {
                    Database.UpdateValue(sqlTransaction, movementItem.IdProduto, movementItem.IdFornecedor, movementItem.Valor);
                    continue;
                }
                var oldValue = oldMovement.ItemMovimento.FindByIdItem(movementItem.IdItem).Valor;
                if(oldValue == movementItem.Valor)
                    continue;
                Database.UpdateValue(sqlTransaction, movementItem.IdProduto, movementItem.IdFornecedor, movementItem.Valor);
            }
        }
        
        private ImpressaoMovimentoDataSet GetPrintMovement(long movementId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetPrintMovement(sqlTransaction, movementId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a impressão do movimento.");
                return new ImpressaoMovimentoDataSet();
            }
        }
        
        private static decimal GetStock(MovimentoDataSet dataset, long productId)
        {
            var stock = 0m;
            foreach (MovimentoDataSet.ItemMovimentoRow movementItem in dataset.ItemMovimento)
            {
                if (movementItem.RowState == DataRowState.Deleted)
                    continue;
                if (movementItem.IdProduto != productId)
                    continue;
                stock += movementItem.Quantidade;
            }
            return stock;
        }

        private decimal GetOldStock(long movementId, long productId)
        {
            var dataSet = GetMovement(movementId);
            return GetStock(dataSet, productId);
        }

        private static decimal GetStock(MovimentoDataSet dataset, long productId, long supplierId)
        {
            var stock = 0m;
            foreach (MovimentoDataSet.ItemMovimentoRow movementItem in dataset.ItemMovimento)
            {
                if (movementItem.RowState == DataRowState.Deleted)
                    continue;
                if (movementItem.IdProduto != productId || movementItem.IdFornecedor != supplierId)
                    continue;
                stock += movementItem.Quantidade;
            }
            return stock;
        }

        private decimal GetOldStock(long movementId, long productId, long supplierId)
        {
            var dataSet = GetMovement(movementId);
            return GetStock(dataSet, productId, supplierId);
        }


        public event EventHandler<EventArgs> MovementSaved;

        public MovimentosDataSet GetMovements()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetMovements(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a lista de movimentos.");
                return new MovimentosDataSet();
            }
        }

        public MovimentoDataSet GetMovement(long movementId)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetMovement(sqlTransaction, movementId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler o movimento.");
                return new MovimentoDataSet();
            }
        }

        public Result IsMovementValid(MovimentoDataSet dataset)
        {
            var result = new Result();
            var movement = dataset.Movimento[0];
            if (movement.IsDataNull()) result.Add("Data", "Por favor, preencha a data.");
            if (!movement.IsNomeNull() && movement.Nome.Length > 50) result.Add("Nome", "O nome excede os 50 caracteres permitidos.");
            if (movement.IsTipoNull()) result.Add("Tipo", "Por favor, indique o tipo.");
            if (!movement.IsTipoNull() && (movement.Tipo == "D" || movement.Tipo == "S") && movement.IsIdEntidadeDestinoNull()) 
                result.Add("IdEntidadeDestino", "Por favor, indique a entidade destino.");
            if (dataset.ItemMovimento.Count == 0)
                result.Add("ItemMovimento", "Por favor, tem de existir pelo menos um detalhe.");
            ClearErrors(dataset.ItemMovimento);
            foreach (MovimentoDataSet.ItemMovimentoRow estimateItem in dataset.ItemMovimento)
            {
                if (estimateItem.RowState == DataRowState.Deleted)
                    continue;
                if (estimateItem.IsIdProdutoNull())
                {
                    estimateItem.SetColumnError("IdProduto", "Por favor, preencha o produto.");
                    result.Add("ItemMovimento.IdProduto", "Por favor, preencha o produto.");
                }
                if (estimateItem.IsIdFornecedorNull())
                {
                    estimateItem.SetColumnError("IdFornecedor", "Por favor, preencha o fornecedor.");
                    result.Add("ItemMovimento.IdFornecedor", "Por favor, preencha o fornecedor.");
                }
                if (estimateItem.IsQuantidadeNull() || estimateItem.Quantidade < 0)
                {
                    estimateItem.SetColumnError("Quantidade", "Por favor, a quantidade não pode ser negativo.");
                    result.Add("ItemMovimento.Quantidade", "Por favor, a quantidade não pode ser negativo.");
                }                
            }
            return result;
        }
        
        public Result IsExitMovementStockValid(MovimentoDataSet dataset)
        {
            var result = new Result();
            foreach(MovimentoDataSet.ItemMovimentoRow movementItem in dataset.ItemMovimento)
            {
                if(movementItem.RowState == DataRowState.Deleted)
                    continue;
                var stock = GetStock(dataset, movementItem.IdProduto);
                var product = GetProduct(movementItem.IdProduto);
                var oldStock = GetOldStock(dataset.Movimento[0].Id, movementItem.IdProduto);
                if (product.Produto[0].Stock - stock + oldStock < 0)
                {
                    result.Add("IdProduto", "O stock do produto '" + product.Produto[0].Referencia + "' é insufuciente para realizar o movimento.");
                    return result;
                }
                var supplierStock = GetStock(dataset, movementItem.IdProduto, movementItem.IdFornecedor);
                var oldSupplierStock = GetOldStock(dataset.Movimento[0].Id, movementItem.IdProduto, movementItem.IdFornecedor);
                var productSupplier = product.Fornecedor.FindByIdProdutoIdFornecedor(movementItem.IdProduto, movementItem.IdFornecedor);
                if (productSupplier.Stock - supplierStock + oldSupplierStock >= 0)
                    continue;
                var supplier = GetSupplier(movementItem.IdFornecedor);
                result.Add("Id", "O stock do produto '" + product.Produto[0].Referencia + "'  do fornecedor '" + supplier.Fornecedor[0].Nome + "' é insufuciente para realizar o movimento.");
                return result;
            }
            return result;
        }

        public Result IsSupplierReturnMovementStockValid(MovimentoDataSet dataset)
        {
            var result = new Result();
            foreach (MovimentoDataSet.ItemMovimentoRow movementItem in dataset.ItemMovimento)
            {
                if (movementItem.RowState == DataRowState.Deleted)
                    continue;
                var stock = GetStock(dataset, movementItem.IdProduto);
                var product = GetProduct(movementItem.IdProduto);
                var oldStock = GetOldStock(dataset.Movimento[0].Id, movementItem.IdProduto);
                if (product.Produto[0].Stock - stock + oldStock < 0)
                {
                    result.Add("IdProduto", "O stock do produto '" + product.Produto[0].Referencia + "' é insufuciente para realizar o movimento.");
                    return result;
                }
                var supplierStock = GetStock(dataset, movementItem.IdProduto, movementItem.IdFornecedor);
                var oldSupplierStock = GetOldStock(dataset.Movimento[0].Id, movementItem.IdProduto, movementItem.IdFornecedor);
                var productSupplier = product.Fornecedor.FindByIdProdutoIdFornecedor(movementItem.IdProduto, movementItem.IdFornecedor);
                if (productSupplier.Stock - supplierStock + oldSupplierStock >= 0)
                    continue;
                var supplier = GetSupplier(movementItem.IdFornecedor);
                result.Add("Id", "O stock do produto '" + product.Produto[0].Referencia + "'  do fornecedor '" + supplier.Fornecedor[0].Nome + "' é insufuciente para realizar o movimento.");
                return result;
            }
            return result;
        }

        public Result IsReturnMovementStockValid(MovimentoDataSet dataset)
        {
            var result = new Result();
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var products = Database.GetProducts(sqlTransaction);
                var suppliers = Database.GetSuppliers(sqlTransaction);
                foreach(MovimentoDataSet.ItemMovimentoRow movementItem in dataset.ItemMovimento)
                {
                    if (!Database.ExistsProductMovements(sqlTransaction, movementItem.MovimentoRow.IdEntidadeDestino, movementItem.IdProduto))
                    {
                        result.Add("IdProduto", "Não existe stock do produto '" + products.Produto.FindById(movementItem.IdProduto).Referencia + "' na entidade destino.");
                        continue;
                    }
                    var stock = GetStock(dataset, movementItem.IdProduto);
                    if (Database.GetTargetEntityProductStock(sqlTransaction, movementItem.MovimentoRow.IdEntidadeDestino, movementItem.IdProduto, movementItem.IdItem) - stock < 0)
                        result.Add("IdProduto", "O stock do produto '" + products.Produto.FindById(movementItem.IdProduto).Referencia + "' é insufuciente para realizar o movimento.");
                    if (!Database.ExistsProductMovements(sqlTransaction, movementItem.MovimentoRow.IdEntidadeDestino, movementItem.IdProduto, movementItem.IdFornecedor))
                    {
                        result.Add("IdProduto", "Não existe stock do produto '" + products.Produto.FindById(movementItem.IdProduto).Referencia + "' do fornecedor '" +
                            suppliers.Fornecedor.FindById(movementItem.IdFornecedor).Nome + "' na entidade destino.");
                        continue;
                    }
                    var supplierStock = GetStock(dataset, movementItem.IdProduto, movementItem.IdFornecedor);
                    if (Database.GetTargetEntityProductStock(sqlTransaction, movementItem.MovimentoRow.IdEntidadeDestino, movementItem.IdProduto, movementItem.IdFornecedor, movementItem.IdItem)
                        - supplierStock < 0)
                        result.Add("IdProduto", "O stock do produto '" + products.Produto.FindById(movementItem.IdProduto).Referencia + "' do fornecedor '" +
                            suppliers.Fornecedor.FindById(movementItem.IdFornecedor).Nome + "' é insufuciente para realizar o movimento.");
                    
                }
                sqlTransaction.Commit();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível validar se o movimento de devolução ao fornecedor é válido.");
            }
            return result;
        }

        public void SaveMovement(MovimentoDataSet dataset)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                RemoveStocks(sqlTransaction, dataset.Movimento[0]);
                UpdateValues(sqlTransaction, dataset.Movimento[0]);
                Database.SaveMovement(sqlTransaction, dataset.Movimento[0]);
                Database.UpdateMovementItems(sqlTransaction, dataset.ItemMovimento);
                AddStocks(sqlTransaction, dataset.Movimento[0]);
                sqlTransaction.Commit();
                OnMovementSaved();
                OnProductSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível gravar o movimento.");
            }
        }

        public void PrintMovement(long movementId)
        {
            var dataSet = GetPrintMovement(movementId);
            if(dataSet.Movimento.Count == 0)
                return;
            var printManager = new PrintManager
            {
                NumberOfCopies = 1,
                PrinterName = GetPrinterName(),
                ReportFile = "Movimento.rpt",
                DataSource = dataSet
            };
            printManager.Print();    
        }

        #endregion

        #region Layout

        private ListaLayoutDataSet GetListLayouts(string list)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetListLayouts(sqlTransaction, list);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a lista de layouts da lista.");
                return new ListaLayoutDataSet();
            }
        }

        private static List<string> GetLayouts(ListaLayoutDataSet dataSet)
        {
            var layouts = new List<string>();
            foreach (ListaLayoutDataSet.ListaLayoutRow layout in dataSet.ListaLayout)
                layouts.Add(layout.Nome);
            return layouts;
        }

        private void SaveLayout(string lista, string nome, string xmlLayout)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.SaveLayout(sqlTransaction, lista, nome, xmlLayout);
                sqlTransaction.Commit();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível gravar layout da lista.");
            }
        }

        public string LoadLayout(GridView gridView, string list)
        {
            try
            {
                var dataSet = GetListLayouts(list);
                var form = new LoadLayoutXtraForm { Layouts = GetLayouts(dataSet) };
                if (form.ShowDialog() != DialogResult.OK)
                    return "";
                var xmlLayout = dataSet.ListaLayout.FindByListaNome(list, form.LayoutName).XmlLayout;
                DevExpressHelper.RestoreLayoutFromXml(gridView, xmlLayout);
                return form.LayoutName;
            }
            catch (Exception)
            {
                return "";
            }
        }

        public string SaveLayout(IWin32Window owner, string list, string xmlLayout)
        {
            try
            {
                var dataSet = GetListLayouts(list);
                var form = new SaveLayoutXtraForm { OwnerWindow = owner, Layouts = GetLayouts(dataSet) };
                if (form.ShowDialog() != DialogResult.OK)
                    return "";
                SaveLayout(list, form.LayoutName, xmlLayout);
                return form.LayoutName;
            }
            catch (Exception)
            {
                return "";
            }
        }

        #endregion

        #region  ProductTree

        public event EventHandler<EventArgs> ProductTreeSaved;

        private void OnProductTreeSaved()
        {
            EventHandler<EventArgs> productTreeSaved = ProductTreeSaved;
            if (productTreeSaved != null)
                productTreeSaved(this, new EventArgs());
        }

        public ArvoreProdutoDataSet ProductsTree()
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                var dataSet = Database.GetProductTree(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a àrvore de produtos.");
                return new ArvoreProdutoDataSet();
            }
        }

        public void UpdateProductTree(long id, string name)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.UpdateProductTree(sqlTransaction, id, name);
                sqlTransaction.Commit();
                OnProductTreeSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível actualizar o nome do nó na àrvore de produtos.");
            }
        }

        public void SaveProductTree(ArvoreProdutoDataSet dataSet)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.SaveProductTree(sqlTransaction, dataSet);
                sqlTransaction.Commit();
                OnProductTreeSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível adicionar os produtos na àrvore de produtos.");
            }
        }

        public void DeleteProductTree(long id)
        {
            FixConnection();
            var sqlTransaction = _connection.BeginTransaction();
            try
            {
                Database.DeleteProductTree(sqlTransaction, id);
                sqlTransaction.Commit();
                OnProductTreeSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível eliminar o nó na àrvore de produtos.");
            }
        }

        #endregion

        public void CloseConnections()
        {
            _connection.Close();            
        }
    }
}
